// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com>

//! BSP Memory Management.

pub mod mmu;

use core::{cell::UnsafeCell, ops::RangeInclusive};

//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------

// Symbols from the linker script.
extern "Rust" {
    static __rx_start: UnsafeCell<()>;
    static __rx_end_exclusive: UnsafeCell<()>;

    static __bss_start: UnsafeCell<u64>;
    static __bss_end_inclusive: UnsafeCell<u64>;
}

//--------------------------------------------------------------------------------------------------
// Public Definitions
//--------------------------------------------------------------------------------------------------

/// The board's physical memory map.
#[rustfmt::skip]
pub(super) mod map {
    /// The inclusive end address of the memory map.
    ///
    /// End address + 1 must be power of two.
    ///
    /// # Note
    ///
    /// RPi3 and RPi4 boards can have different amounts of RAM. To make our code lean for
    /// educational purposes, we set the max size of the address space to 4 GiB regardless of board.
    /// This way, we can map the entire range that we need (end of MMIO for RPi4) in one take.
    ///
    /// However, making this trade-off has the downside of making it possible for the CPU to assert a
    /// physical address that is not backed by any DRAM (e.g. accessing an address close to 4 GiB on
    /// an RPi3 that comes with 1 GiB of RAM). This would result in a crash or other kind of error.
    pub const END_INCLUSIVE:       usize = 0xFFFF_FFFF;

    pub const GPIO_OFFSET:         usize = 0x0020_0000;
    pub const UART_OFFSET:         usize = 0x0020_1000;

    /// Physical devices.
    #[cfg(feature = "bsp_rpi3")]
    pub mod mmio {
        use super::*;

        pub const START:                                 usize =         0x3F00_0000;
        pub const PERIPHERAL_INTERRUPT_CONTROLLER_START: usize = START + 0x0000_B200;
        pub const GPIO_START:                            usize = START + GPIO_OFFSET;
        pub const PL011_UART_START:                      usize = START + UART_OFFSET;
        pub const LOCAL_INTERRUPT_CONTROLLER_START:      usize =         0x4000_0000;
        pub const END_INCLUSIVE:                         usize =         0x4000_FFFF;
    }

    /// Physical devices.
    #[cfg(feature = "bsp_rpi4")]
    pub mod mmio {
        use super::*;

        pub const START:            usize =         0xFE00_0000;
        pub const GPIO_START:       usize = START + GPIO_OFFSET;
        pub const PL011_UART_START: usize = START + UART_OFFSET;
        pub const GICD_START:       usize =         0xFF84_1000;
        pub const GICC_START:       usize =         0xFF84_2000;
        pub const END_INCLUSIVE:    usize =         0xFF84_FFFF;
    }
}

//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------

/// Start address of the Read+Execute (RX) range.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn rx_start() -> usize {
    unsafe { __rx_start.get() as usize }
}

/// Exclusive end address of the Read+Execute (RX) range.
///
/// # Safety
///
/// - Value is provided by the linker script and must be trusted as-is.
#[inline(always)]
fn rx_end_exclusive() -> usize {
    unsafe { __rx_end_exclusive.get() as usize }
}

//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------

/// Return the inclusive range spanning the .bss section.
///
/// # Safety
///
/// - Values are provided by the linker script and must be trusted as-is.
/// - The linker-provided addresses must be u64 aligned.
pub fn bss_range_inclusive() -> RangeInclusive<*mut u64> {
    let range;
    unsafe {
        range = RangeInclusive::new(__bss_start.get(), __bss_end_inclusive.get());
    }
    assert!(!range.is_empty());

    range
}
